
/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 *版权所有2002-2021原作者。
 *
 *根据Apache许可证2.0版（“许可证”）许可；
 *除非符合许可证的规定，否则您不得使用此文件。
 *您可以在以下网址获取许可证副本
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *除非适用法律要求或书面同意，否则软件
 *根据许可证分发是在“按原样”的基础上分发的，
 *无任何明示或暗示的保证或条件。
 *有关管理权限的特定语言，请参阅许可证
 *许可证下的限制。
 */

package org.springframework.context.support;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * {@link org.springframework.context.MessageSource} implementation that
 * accesses resource bundles using specified basenames. This class relies
 * on the underlying JDK's {@link java.util.ResourceBundle} implementation,
 * in combination with the JDK's standard message parsing provided by
 * {@link java.text.MessageFormat}.
 *
 * <p>This MessageSource caches both the accessed ResourceBundle instances and
 * the generated MessageFormats for each message. It also implements rendering of
 * no-arg messages without MessageFormat, as supported by the AbstractMessageSource
 * base class. The caching provided by this MessageSource is significantly faster
 * than the built-in caching of the {@code java.util.ResourceBundle} class.
 *
 * <p>The basenames follow {@link java.util.ResourceBundle} conventions: essentially,
 * a fully-qualified classpath location. If it doesn't contain a package qualifier
 * (such as {@code org.mypackage}), it will be resolved from the classpath root.
 * Note that the JDK's standard ResourceBundle treats dots as package separators:
 * This means that "test.theme" is effectively equivalent to "test/theme".
 *
 * <p>On the classpath, bundle resources will be read with the locally configured
 * {@link #setDefaultEncoding encoding}: by default, ISO-8859-1; consider switching
 * this to UTF-8, or to {@code null} for the platform default encoding. On the JDK 9+
 * module path where locally provided {@code ResourceBundle.Control} handles are not
 * supported, this MessageSource always falls back to {@link ResourceBundle#getBundle}
 * retrieval with the platform default encoding: UTF-8 with a ISO-8859-1 fallback on
 * JDK 9+ (configurable through the "java.util.PropertyResourceBundle.encoding" system
 * property). Note that {@link #loadBundle(Reader)}/{@link #loadBundle(InputStream)}
 * won't be called in this case either, effectively ignoring overrides in subclasses.
 * Consider implementing a JDK 9 {@code java.util.spi.ResourceBundleProvider} instead.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Qimiao Chen
 * @see #setBasenames
 * @see ReloadableResourceBundleMessageSource
 * @see java.util.ResourceBundle
 * @see java.text.MessageFormat
 */
/**
 *｛@link org.springframework.context.MessageSource｝实现
 *使用指定的基名称访问资源捆绑包。这个类依赖
 *在底层JDK的{@link java.util.ResourceBundle}实现上，
 *结合JDK提供的标准消息解析
 *｛@link java.text.MessageFormat｝。
 *
 *<p>此MessageSource缓存访问的ResourceBundle实例和
 *为每条消息生成的MessageFormats。它还实现了
 *没有AbstractMessageSource支持的不带MessageFormat的arg消息
 *基类。此MessageSource提供的缓存速度明显更快
 *而不是{@code java.util.ResourceBundle}类的内置缓存。
 *
 *<p>基本名称遵循{@link java.util.ResourceBundle}约定：本质上，
 *完全限定的类路径位置。如果它不包含包限定符
 *（例如{@code.org.mypackage}），它将从类路径根解析。
 *请注意，JDK的标准ResourceBundle将点视为包分隔符：
 *这意味着“test.theme”实际上等同于“test/theme”。
 *
 *<p>在类路径上，将使用本地配置的
 *｛@link#setDefaultEncoding encoding｝：默认情况下，ISO-8859-1；考虑切换
 *将其转换为UTF-8，或对于平台默认编码转换为{@code-null}。在JDK 9上+
 *模块路径，其中本地提供的｛@code ResourceBundle.Control｝句柄不是
 *支持，此MessageSource总是回退到｛@link ResourceBundle#getBundle｝
 *使用平台默认编码进行检索：UTF-8，启用ISO-8859-1回退
 *JDK 9+（可通过“java.util.PropertyResourceBundle.conding”系统进行配置
 *财产）。请注意，｛@link#loadBundle（Reader）｝/｛@link#loadBundle（InputStream）｝
 *在这种情况下也不会调用，有效地忽略了子类中的重写。
 *考虑实现JDK 9｛@code java.util.spi.ResourceBundleProvider｝。
 *
 *@作者罗德·约翰逊
 *@作者Juergen Hoeller
 *@作者陈奇苗
 *@参见#setBasenames
 *@请参阅可重新加载的资源捆绑消息源
 *@参见java.util.ResourceBundle
 *@参见java.text.MessageFormat
 */
public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements BeanClassLoaderAware {

	@Nullable
	private ClassLoader bundleClassLoader;

	@Nullable
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

	/**
	 * Cache to hold loaded ResourceBundles.
	 * This Map is keyed with the bundle basename, which holds a Map that is
	 * keyed with the Locale and in turn holds the ResourceBundle instances.
	 * This allows for very efficient hash lookups, significantly faster
	 * than the ResourceBundle class's own cache.
	 */
	/**
	 *缓存以容纳已加载的ResourceBundle。
	 *此Map使用bundle basename键控，该名称包含一个Map
	 *用Locale键控，然后保存ResourceBundle实例。
	 *这允许非常高效的哈希查找，速度明显更快
	 *而不是ResourceBundle类自己的缓存。
	 */
	private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles =
			new ConcurrentHashMap<>();

	/**
	 * Cache to hold already generated MessageFormats.
	 * This Map is keyed with the ResourceBundle, which holds a Map that is
	 * keyed with the message code, which in turn holds a Map that is keyed
	 * with the Locale and holds the MessageFormat values. This allows for
	 * very efficient hash lookups without concatenated keys.
	 * @see #getMessageFormat
	 */
	/**
	 *缓存以保存已生成的MessageFormats。
	 *这个Map是用ResourceBundle键控的，它包含一个Map
	 *用消息代码键控，消息代码又保存一个键控的Map
	 *使用Locale并保存MessageFormat值。这允许
	 *非常有效的散列查找，而不需要连接键。
	 *@参见#getMessageFormat
	 */
	private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats =
			new ConcurrentHashMap<>();

	@Nullable
	private volatile MessageSourceControl control = new MessageSourceControl();


	public ResourceBundleMessageSource() {
		setDefaultEncoding("ISO-8859-1");
	}


	/**
	 * Set the ClassLoader to load resource bundles with.
	 * <p>Default is the containing BeanFactory's
	 * {@link org.springframework.beans.factory.BeanClassLoaderAware bean ClassLoader},
	 * or the default ClassLoader determined by
	 * {@link org.springframework.util.ClassUtils#getDefaultClassLoader()}
	 * if not running within a BeanFactory.
	 */
	/**
	 *将ClassLoader设置为使用加载资源捆绑包。
	 *＜p＞默认为包含BeanFactory的
	 *｛@link org.springframework.beans.factory.BeanClassLoaderAware bean ClassLoader｝，
	 *或默认ClassLoader，由
	 *｛@link org.springframework.util.ClassUtils#getDefaultClassLoader（）｝
	 *如果不是在BeanFactory中运行。
	 */
	public void setBundleClassLoader(ClassLoader classLoader) {
		this.bundleClassLoader = classLoader;
	}

	/**
	 * Return the ClassLoader to load resource bundles with.
	 * <p>Default is the containing BeanFactory's bean ClassLoader.
	 * @see #setBundleClassLoader
	 */
	/**
	 *返回用于加载资源捆绑包的ClassLoader。
	 *＜p＞默认值是包含BeanFactory的bean ClassLoader。
	 *@参见#setBundleClassLoader
	 */
	@Nullable
	protected ClassLoader getBundleClassLoader() {
		return (this.bundleClassLoader != null ? this.bundleClassLoader : this.beanClassLoader);
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}


	/**
	 * Resolves the given message code as key in the registered resource bundles,
	 * returning the value found in the bundle as-is (without MessageFormat parsing).
	 */
	/**
	 *将给定的消息代码解析为已注册资源束中的密钥，
	 *按原样返回在捆绑包中找到的值（不进行MessageFormat解析）。
	 */
	@Override
	protected String resolveCodeWithoutArguments(String code, Locale locale) {
		Set<String> basenames = getBasenameSet();
		for (String basename : basenames) {
			ResourceBundle bundle = getResourceBundle(basename, locale);
			if (bundle != null) {
				String result = getStringOrNull(bundle, code);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

	/**
	 * Resolves the given message code as key in the registered resource bundles,
	 * using a cached MessageFormat instance per message code.
	 */
	/**
	 *将给定的消息代码解析为已注册资源束中的密钥，
	 *每个消息代码使用缓存的MessageFormat实例。
	 */
	@Override
	@Nullable
	protected MessageFormat resolveCode(String code, Locale locale) {
		Set<String> basenames = getBasenameSet();
		for (String basename : basenames) {
			ResourceBundle bundle = getResourceBundle(basename, locale);
			if (bundle != null) {
				MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
				if (messageFormat != null) {
					return messageFormat;
				}
			}
		}
		return null;
	}


	/**
	 * Return a ResourceBundle for the given basename and Locale,
	 * fetching already generated ResourceBundle from the cache.
	 * @param basename the basename of the ResourceBundle
	 * @param locale the Locale to find the ResourceBundle for
	 * @return the resulting ResourceBundle, or {@code null} if none
	 * found for the given basename and Locale
	 */
	/**
	 *返回给定basename和Locale的ResourceBundle，
	 *正在从缓存中获取已生成的ResourceBundle。
	 *@param basename资源束的基本名称
	 *@param locale用于查找ResourceBundle的区域设置
	 *@返回生成的ResourceBundle，或者｛@code null｝（如果没有）
	 *为给定的basename和Locale找到
	 */
	@Nullable
	protected ResourceBundle getResourceBundle(String basename, Locale locale) {
		if (getCacheMillis() >= 0) {
			// Fresh ResourceBundle.getBundle call in order to let ResourceBundle
			// Fresh ResourceBundle.getBundle调用以便让ResourceBundle
			// do its native caching, at the expense of more extensive lookup steps.
			// 以牺牲更广泛的查找步骤为代价，进行本机缓存。
			return doGetBundle(basename, locale);
		}
		else {
			// Cache forever: prefer locale cache over repeated getBundle calls.
			// 永远缓存：比起重复的getBundle调用，更喜欢区域设置缓存。
			Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
			if (localeMap != null) {
				ResourceBundle bundle = localeMap.get(locale);
				if (bundle != null) {
					return bundle;
				}
			}
			try {
				ResourceBundle bundle = doGetBundle(basename, locale);
				if (localeMap == null) {
					localeMap = this.cachedResourceBundles.computeIfAbsent(basename, bn -> new ConcurrentHashMap<>());
				}
				localeMap.put(locale, bundle);
				return bundle;
			}
			catch (MissingResourceException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
				}
				// Assume bundle not found
				// 假设未找到捆绑包
				// -> do NOT throw the exception to allow for checking parent message source.
				// ->不要抛出异常以允许检查父消息源。
				return null;
			}
		}
	}

	/**
	 * Obtain the resource bundle for the given basename and Locale.
	 * @param basename the basename to look for
	 * @param locale the Locale to look for
	 * @return the corresponding ResourceBundle
	 * @throws MissingResourceException if no matching bundle could be found
	 * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader)
	 * @see #getBundleClassLoader()
	 */
	/**
	 *获取给定basename和Locale的资源捆绑包。
	 *@param basename要查找的basename
	 *@param locale要查找的区域设置
	 *@返回相应的ResourceBundle
	 *如果找不到匹配的捆绑包，@throws MissingResourceException
	 *@请参阅java.util.ResourceBundle#getBundle（String，Locale，ClassLoader）
	 *@参见#getBundleClassLoader（）
	 */
	protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
		ClassLoader classLoader = getBundleClassLoader();
		Assert.state(classLoader != null, "No bundle ClassLoader set");

		MessageSourceControl control = this.control;
		if (control != null) {
			try {
				return ResourceBundle.getBundle(basename, locale, classLoader, control);
			}
			catch (UnsupportedOperationException ex) {
				// Probably in a Jigsaw environment on JDK 9+
				// 可能在JDK 9上的Jigsaw环境中+
				this.control = null;
				String encoding = getDefaultEncoding();
				if (encoding != null && logger.isInfoEnabled()) {
					logger.info("ResourceBundleMessageSource is configured to read resources with encoding '" +
							encoding + "' but ResourceBundle.Control not supported in current system environment: " +
							ex.getMessage() + " - falling back to plain ResourceBundle.getBundle retrieval with the " +
							"platform default encoding. Consider setting the 'defaultEncoding' property to 'null' " +
							"for participating in the platform default and therefore avoiding this log message.");
				}
			}
		}

		// Fallback: plain getBundle lookup without Control handle
		// 回退：没有Control句柄的普通getBundle查找
		return ResourceBundle.getBundle(basename, locale, classLoader);
	}

	/**
	 * Load a property-based resource bundle from the given reader.
	 * <p>This will be called in case of a {@link #setDefaultEncoding "defaultEncoding"},
	 * including {@link ResourceBundleMessageSource}'s default ISO-8859-1 encoding.
	 * Note that this method can only be called with a {@code ResourceBundle.Control}:
	 * When running on the JDK 9+ module path where such control handles are not
	 * supported, any overrides in custom subclasses will effectively get ignored.
	 * <p>The default implementation returns a {@link PropertyResourceBundle}.
	 * @param reader the reader for the target resource
	 * @return the fully loaded bundle
	 * @throws IOException in case of I/O failure
	 * @since 4.2
	 * @see #loadBundle(InputStream)
	 * @see PropertyResourceBundle#PropertyResourceBundle(Reader)
	 */
	/**
	 *从给定的读取器加载基于属性的资源捆绑包。
	 *<p>在｛@link#setDefaultEncoding“defaultEncoding”｝的情况下将调用此函数，
	 *包括{@link ResourceBundleMessageSource}的默认ISO-8859-1编码。
	 *请注意，此方法只能使用｛@code ResourceBundle.Control｝调用：
	 *当在JDK 9+模块路径上运行时，这些控制句柄没有
	 *受支持，自定义子类中的任何重写都将被有效地忽略。
	 *＜p＞默认实现返回一个｛@link PropertyResourceBundle｝。
	 *@param reader目标资源的读取器
	 *@返回满载的捆绑包
	 *在I/O失败的情况下@throws IOException
	 *@自4.2起
	 *@参见#loadBundle（输入流）
	 *@see-PropertyResourceBundle#PropertyResourceBundle（阅读器）
	 */
	protected ResourceBundle loadBundle(Reader reader) throws IOException {
		return new PropertyResourceBundle(reader);
	}

	/**
	 * Load a property-based resource bundle from the given input stream,
	 * picking up the default properties encoding on JDK 9+.
	 * <p>This will only be called with {@link #setDefaultEncoding "defaultEncoding"}
	 * set to {@code null}, explicitly enforcing the platform default encoding
	 * (which is UTF-8 with a ISO-8859-1 fallback on JDK 9+ but configurable
	 * through the "java.util.PropertyResourceBundle.encoding" system property).
	 * Note that this method can only be called with a {@code ResourceBundle.Control}:
	 * When running on the JDK 9+ module path where such control handles are not
	 * supported, any overrides in custom subclasses will effectively get ignored.
	 * <p>The default implementation returns a {@link PropertyResourceBundle}.
	 * @param inputStream the input stream for the target resource
	 * @return the fully loaded bundle
	 * @throws IOException in case of I/O failure
	 * @since 5.1
	 * @see #loadBundle(Reader)
	 * @see PropertyResourceBundle#PropertyResourceBundle(InputStream)
	 */
	/**
	 *从给定的输入流加载基于属性的资源束，
	 *获取JDK 9+上的默认属性编码。
	 *<p>这将仅使用｛@link#setDefaultEncoding“defaultEncoding”｝调用
	 *设置为｛@code null｝，显式强制执行平台默认编码
	 *（它是UTF-8，在JDK 9+上有ISO-8859-1回退，但可以配置
	 *通过“java.util.PropertyResourceBundle.conding”系统属性）。
	 *请注意，此方法只能使用｛@code ResourceBundle.Control｝调用：
	 *当在JDK 9+模块路径上运行时，这些控制句柄没有
	 *受支持，自定义子类中的任何重写都将被有效地忽略。
	 *＜p＞默认实现返回一个｛@link PropertyResourceBundle｝。
	 *@param inputStream目标资源的输入流
	 *@返回满载的捆绑包
	 *在I/O失败的情况下@throws IOException
	 *@自5.1起
	 *@参见#loadBundle（阅读器）
	 *@see-PropertyResourceBundle#PropertyResourceBundle（输入流）
	 */
	protected ResourceBundle loadBundle(InputStream inputStream) throws IOException {
		return new PropertyResourceBundle(inputStream);
	}

	/**
	 * Return a MessageFormat for the given bundle and code,
	 * fetching already generated MessageFormats from the cache.
	 * @param bundle the ResourceBundle to work on
	 * @param code the message code to retrieve
	 * @param locale the Locale to use to build the MessageFormat
	 * @return the resulting MessageFormat, or {@code null} if no message
	 * defined for the given code
	 * @throws MissingResourceException if thrown by the ResourceBundle
	 */
	/**
	 *返回给定捆绑包和代码的MessageFormat，
	 *正在从缓存中获取已生成的MessageFormats。
	 *@param bundle要处理的ResourceBundle
	 *@param code要检索的消息代码
	 *@param locale用于生成MessageFormat的区域设置
	 *@返回生成的MessageFormat，或者｛@code null｝（如果没有消息）
	 *为给定代码定义
	 *@throws MissingResourceException（如果由ResourceBundle引发）
	 */
	@Nullable
	protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
			throws MissingResourceException {

		Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
		Map<Locale, MessageFormat> localeMap = null;
		if (codeMap != null) {
			localeMap = codeMap.get(code);
			if (localeMap != null) {
				MessageFormat result = localeMap.get(locale);
				if (result != null) {
					return result;
				}
			}
		}

		String msg = getStringOrNull(bundle, code);
		if (msg != null) {
			if (codeMap == null) {
				codeMap = this.cachedBundleMessageFormats.computeIfAbsent(bundle, b -> new ConcurrentHashMap<>());
			}
			if (localeMap == null) {
				localeMap = codeMap.computeIfAbsent(code, c -> new ConcurrentHashMap<>());
			}
			MessageFormat result = createMessageFormat(msg, locale);
			localeMap.put(locale, result);
			return result;
		}

		return null;
	}

	/**
	 * Efficiently retrieve the String value for the specified key,
	 * or return {@code null} if not found.
	 * <p>As of 4.2, the default implementation checks {@code containsKey}
	 * before it attempts to call {@code getString} (which would require
	 * catching {@code MissingResourceException} for key not found).
	 * <p>Can be overridden in subclasses.
	 * @param bundle the ResourceBundle to perform the lookup in
	 * @param key the key to look up
	 * @return the associated value, or {@code null} if none
	 * @since 4.2
	 * @see ResourceBundle#getString(String)
	 * @see ResourceBundle#containsKey(String)
	 */
	/**
	 *有效地检索指定键的String值，
	 *如果未找到，则返回｛@code null｝。
	 *＜p＞从4.2开始，默认实现检查｛@code containsKey｝
	 *在尝试调用｛@code getString｝（这需要
	 *正在捕获密钥的｛@code MissingResourceException｝）。
	 *＜p＞可以在子类中重写。
	 *@param bundle要在中执行查找的ResourceBundle
	 *@param key要查找的键
	 *@返回关联的值，如果没有，则返回｛@code null｝
	 *@自4.2起
	 *@see ResourceBundle#getString（字符串）
	 *@see ResourceBundle#containsKey（字符串）
	 */
	@Nullable
	protected String getStringOrNull(ResourceBundle bundle, String key) {
		if (bundle.containsKey(key)) {
			try {
				return bundle.getString(key);
			}
			catch (MissingResourceException ex) {
				// Assume key not found for some other reason
				// 假设由于其他原因未找到密钥
				// -> do NOT throw the exception to allow for checking parent message source.
				// ->不要抛出异常以允许检查父消息源。
			}
		}
		return null;
	}

	/**
	 * Show the configuration of this MessageSource.
	 */
	/**
	 *显示此MessageSource的配置。
	 */
	@Override
	public String toString() {
		return getClass().getName() + ": basenames=" + getBasenameSet();
	}


	/**
	 * Custom implementation of {@code ResourceBundle.Control}, adding support
	 * for custom file encodings, deactivating the fallback to the system locale
	 * and activating ResourceBundle's native cache, if desired.
	 */
	/**
	 *｛@code ResourceBundle.Control｝的自定义实现，添加了支持
	 *对于自定义文件编码，停用到系统区域设置的回退
	 *并在需要时激活ResourceBundle的本地缓存。
	 */
	private class MessageSourceControl extends ResourceBundle.Control {

		@Override
		@Nullable
		public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
				throws IllegalAccessException, InstantiationException, IOException {

			// Special handling of default encoding
			// 默认编码的特殊处理
			if (format.equals("java.properties")) {
				String bundleName = toBundleName(baseName, locale);
				final String resourceName = toResourceName(bundleName, "properties");
				final ClassLoader classLoader = loader;
				final boolean reloadFlag = reload;
				InputStream inputStream = null;
				if (reloadFlag) {
					URL url = classLoader.getResource(resourceName);
					if (url != null) {
						URLConnection connection = url.openConnection();
						if (connection != null) {
							connection.setUseCaches(false);
							inputStream = connection.getInputStream();
						}
					}
				}
				else {
					inputStream = classLoader.getResourceAsStream(resourceName);
				}
				if (inputStream != null) {
					String encoding = getDefaultEncoding();
					if (encoding != null) {
						try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
							return loadBundle(bundleReader);
						}
					}
					else {
						try (InputStream bundleStream = inputStream) {
							return loadBundle(bundleStream);
						}
					}
				}
				else {
					return null;
				}
			}
			else {
				// Delegate handling of "java.class" format to standard Control
				// 将“java.class”格式的处理委托给标准控件
				return super.newBundle(baseName, locale, format, loader, reload);
			}
		}

		@Override
		@Nullable
		public Locale getFallbackLocale(String baseName, Locale locale) {
			Locale defaultLocale = getDefaultLocale();
			return (defaultLocale != null && !defaultLocale.equals(locale) ? defaultLocale : null);
		}

		@Override
		public long getTimeToLive(String baseName, Locale locale) {
			long cacheMillis = getCacheMillis();
			return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale));
		}

		@Override
		public boolean needsReload(
				String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {

			if (super.needsReload(baseName, locale, format, loader, bundle, loadTime)) {
				cachedBundleMessageFormats.remove(bundle);
				return true;
			}
			else {
				return false;
			}
		}
	}

}
